﻿Imports System
Imports System.IO
Imports System.Net
Imports System.Text
Imports System.Web.Script.Serialization

Public Class cURLPush

    Private _SyncID As Integer
    Private _ProjectID As String
    Private _DataTemplateID As Integer
    Private _FileName As String
    Private _LastRecordDateTime As DateTime
    Private _TotalRows As Integer
    Private _TotalColumns As Integer
    Private _TotalRecords As Integer

    Public Property SyncID As Integer
        Get
            SyncID = _SyncID
        End Get
        Set(ByVal value As Integer)
            _SyncID = value
        End Set
    End Property

    Public Property ProjectID As String
        Get
            ProjectID = _ProjectID
        End Get
        Set(ByVal value As String)
            _ProjectID = value
        End Set
    End Property

    Public Property DataTemplateID As Integer
        Get
            DataTemplateID = _DataTemplateID
        End Get
        Set(ByVal value As Integer)
            _DataTemplateID = value
        End Set
    End Property

    Public Property FileName As String
        Get
            FileName = _FileName
        End Get
        Set(ByVal value As String)
            _FileName = value
        End Set
    End Property

    Public Property LastRecordDateTime As DateTime
        Get
            LastRecordDateTime = _LastRecordDateTime
        End Get
        Set(ByVal value As DateTime)
            _LastRecordDateTime = value
        End Set
    End Property

    Public Property TotalRows As Integer
        Get
            TotalRows = _TotalRows
        End Get
        Set(ByVal value As Integer)
            _TotalRows = value
        End Set
    End Property

    Public Property TotalColumns As Integer
        Get
            TotalColumns = _TotalColumns
        End Get
        Set(ByVal value As Integer)
            _TotalColumns = value
        End Set
    End Property

    Private ReadOnly Property TotalRecords As Integer
        Get
            TotalRecords = _TotalRows * _TotalColumns
        End Get
    End Property

    Public Sub New(ByVal SyncID As Integer, ByVal ProjectID As String, ByVal DataTemplateID As Integer, ByVal FileName As String, ByVal LastRecordDateTime As DateTime, ByVal TotalRows As Integer, ByVal TotalColumns As Integer)
        _SyncID = SyncID
        _ProjectID = ProjectID
        _DataTemplateID = DataTemplateID
        _FileName = FileName
        _LastRecordDateTime = LastRecordDateTime
        _TotalRows = TotalRows
        _TotalColumns = TotalColumns
    End Sub

    ''' <summary>
    ''' Curls the latest data load to the VOEIS hub.
    ''' </summary>
    ''' <param name="ms">MemoryStream that holds the current file data.</param>
    ''' <remarks>This is the workhorse method for pushing the data to the hub - it builds the request and consumes the response.</remarks>
    Public Sub Curl2Hub(ByVal ms As MemoryStream)
        'Disable this sync in the metadata so that we don't try to 'overpush' the currently running thread.
        Dim dw As New DataWriter(_SyncID)

        'Set up some of our basic variables
        Dim HubURL As String = My.Settings.VOEISHubURL.Replace("<projectID>", _ProjectID) & "upload_logger_data.json?api_key=" & My.Settings.APIKey

        'Set up the header boundary
        Dim boundary As String = "----------------------------" & DateTime.Now.Ticks.ToString("x")

        'Set up the boundary sentences as byte arrays
        Dim boundarybytes As Byte() = System.Text.Encoding.ASCII.GetBytes("--" & boundary & Environment.NewLine)
        Dim boundaryBytes2 As Byte() = Text.Encoding.ASCII.GetBytes(Environment.NewLine & "--" & boundary & Environment.NewLine)

        'Notify via the event log regarding what I am doing
        System.Diagnostics.EventLog.WriteEntry("VOEIS Hub Push Service", String.Format("Uploading {0} to {1}", _FileName, HubURL))

        'Set security protocols
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3

        'Create the web request and set some basic parameters
        Dim request As HttpWebRequest = DirectCast(WebRequest.Create(New Uri(HubURL, UriKind.Absolute)), HttpWebRequest)

        'Set the timeout for the response
        'These values are experimental and based on my observations as to processing time which is about 1 record/2 milliseconds on 4/15/2011.  An additional 5 minute timeout will be added to account for small recordsets.
        '9/13/2011 - the timeout has changed due to changes on the server that have resulted in up to a 5000X increase in the amount of processing time required to process an upload (2 ms/record to 200 ms/record)
        '9/15/2011: Due to new informationa about the hub resources, I am going to set this to a static number right now (30 min timeout).  Hopefully they will receive more resources and we can go back to a dynamic calculation.
        '10/3/2011: This is going back to a dynamically rendered timeout - the hub is more responsive and seems to be working better now.  I am using a default of 30 seconds/record and a buffer of 10 minutes.
        Dim myTimeout As Int32 = 2147483
        '10 minute buffer + 30 seconds/record
        Dim calcTimeout As Long = ((600) + (TotalRecords * 30))

        If calcTimeout > myTimeout Then
            request.Timeout = myTimeout * 1000
        Else
            request.Timeout = calcTimeout * 1000
        End If

        request.Method = "POST"
        request.AllowAutoRedirect = True
        request.KeepAlive = True
        request.Credentials = CredentialCache.DefaultCredentials
        request.ContentType = "multipart/form-data; boundary=" & boundary

        'Set up the request stream
        Dim requestStream As Stream = request.GetRequestStream()

        'Write the first boundary
        requestStream.Write(boundarybytes, 0, boundarybytes.Length)

        'Begin header setup for the filestream upload
        'Setup the template
        Dim FileStreamHeaderTemplate As String = "Content-Disposition: form-data; name=""{0}""; filename=""{1}""" & Environment.NewLine & "Content-Type: {2}" & Environment.NewLine & Environment.NewLine
        'Create the header string
        Dim FileStreamHeader As String = String.Format(FileStreamHeaderTemplate, "datafile", _FileName, "application/octet-stream")
        'Convert the header to a byte array
        Dim FileStreamHeaderBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(FileStreamHeader)
        'Write the header bytes to the request stream
        requestStream.Write(FileStreamHeaderBytes, 0, FileStreamHeaderBytes.Length)

        'Declare our buffer byte array
        Dim buffer As Byte() = New Byte(4095) {}
        'Setup a counter to track the bytes read
        Dim bytesRead As Integer = 0
        'Begin outputting the memory stream to the request stream
        While (InlineAssignHelper(bytesRead, ms.Read(buffer, 0, buffer.Length))) <> 0
            requestStream.Write(buffer, 0, bytesRead)
        End While
        'Close the memory stream
        ms.Close()

        'Write the boundary header to the request stream
        'We are having issues when there is only one record being sent.  I am going to add in a Environment.NewLine to see the effect.
        requestStream.Write(boundaryBytes2, 0, boundaryBytes2.Length)

        'Begin header setup for the form field upload
        'Setup the form data template
        Dim FormDataTemplate As String = "Content-Disposition: form-data; name=""{0}""" & Environment.NewLine & Environment.NewLine & "{1}"
        'Setup the form header string
        Dim FormItem As String = String.Format(FormDataTemplate, "data_template_id", _DataTemplateID.ToString)
        'Convert the header string to a byte array
        Dim FormItemBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(FormItem)
        'Write the header bytes to the request stream
        requestStream.Write(FormItemBytes, 0, FormItemBytes.Length)

        'Write the trailing boundary
        'Setup the trailing boundary byte array
        Dim trailer As Byte() = System.Text.Encoding.ASCII.GetBytes(Environment.NewLine & "--" & boundary & "--" & Environment.NewLine)
        'Write the trailing boundary byte array to the request stream
        requestStream.Write(trailer, 0, trailer.Length)
        'Close the request stream
        requestStream.Close()

        'Begin receiving our response
        Dim response As WebResponse = Nothing
        Try
            EventLog.WriteEntry("VOEIS Hub Push Service", "Waiting for response for stream after uploading " & FileName)
            'Instantiate the response
            response = request.GetResponse()
            'Setup the response stream
            Dim responseStream As Stream = response.GetResponseStream()

            'Begin a stream reader for the response stream
            Dim sr As New StreamReader(responseStream)
            Dim responseString As String = sr.ReadToEnd()

            'Write the response stream to the event log
            System.Diagnostics.EventLog.WriteEntry("VOEIS Hub Push Service", String.Format("File uploaded, server response is: {0}", responseString))

            'Get the LastRecordDateTime from the JSON string, otherwise it will default to 1/1/1900 00:00:00
            If Not responseString.Contains("error") Then
                Dim serializer As JavaScriptSerializer = New JavaScriptSerializer()
                Dim lstResponse As Object = serializer.DeserializeObject("[" & responseString & "]")
                GetJSONTimeStamp(lstResponse)
            End If

            'Log the response data in the database
            dw.UpdateSyncMetadata(LastRecordDateTime, responseString)

        Catch ex As Exception
            'Catch our errors and write them to the event log and send an email
            System.Diagnostics.EventLog.WriteEntry("VOEIS Hub Push Service", "Error in cURLPush class" & Environment.NewLine & "Error uploading file: " & Environment.NewLine & ex.Message.ToString, EventLogEntryType.Error)

            'TODO: Email the exception and setup the current state
            Dim mailer As New NotifyMail()
            mailer.SendServiceMail("VOEIS Hub Push Service", ex.Message.ToString)

        Finally
            'Make sure that we close the response if it is still alive
            If response IsNot Nothing Then
                response.Close()
                response = Nothing
            End If

            'Make sure that we close our request
            request = Nothing
        End Try
    End Sub

    ''' <summary>
    ''' Parses data byte wise.
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="target"></param>
    ''' <param name="value"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, ByVal value As T) As T
        target = value
        Return value
    End Function

    ''' <summary>
    ''' Gets the timestamp from the returned JSON string.
    ''' </summary>
    ''' <param name="lstObjs">This needs to be a dictionary object derived from the JSON output</param>
    ''' <remarks>This method is used so that the VOEISPushService metadata table can be updated with the latest metadata.</remarks>
    Private Sub GetJSONTimeStamp(ByVal lstObjs As Object)
        Dim ts As String = ""


        For Each myObj As Object In lstObjs

            If Not myObj.[GetType]() Is GetType(System.Collections.Generic.KeyValuePair(Of String, Object)) Then
                If DirectCast(myObj, IDictionary(Of String, Object)).ContainsKey("local_date_time") Then
                    ts = DirectCast(myObj, IDictionary(Of String, Object))("local_date_time").ToString()
                End If

                For Each kvp As KeyValuePair(Of String, Object) In DirectCast(myObj, IDictionary(Of String, Object))
                    If kvp.Value.[GetType]() Is GetType(System.Collections.Generic.Dictionary(Of String, Object)) Then
                        GetJSONTimeStamp(CType(kvp.Value, IDictionary(Of String, Object)))
                    ElseIf kvp.Key = "local_date_time" Then
                        ts = Strings.Left(kvp.Value.ToString, kvp.Value.ToString.Length - 6)
                        _LastRecordDateTime = ts
                    End If
                Next
            Else
                If DirectCast(myObj, KeyValuePair(Of String, Object)).Key = "local_date_time" Then

                    ts = Strings.Left(DirectCast(myObj, KeyValuePair(Of String, Object)).Value.ToString, DirectCast(myObj, KeyValuePair(Of String, Object)).Value.ToString.Length - 6)
                    _LastRecordDateTime = ts
                End If
            End If
        Next
    End Sub

End Class
